iT邦幫忙

2024 iThome 鐵人賽

DAY 7
1
JavaScript

TypeScript 初學者也能看的學習指南系列 第 7

TypeScript 初學者也能看的學習指南 07 - Tuple 元組

  • 分享至 

  • xImage
  •  

https://ithelp.ithome.com.tw/upload/images/20240917/20149362U3rgzYmWUl.png

在純 JavaScript 中沒有支援「元組」的寫法,不過其實在有些程式語言中是有的,如 Python, Rust, C#
本篇將來帶你了解「元組」基本概念,並且透過情境範例去了解其特性和須注意之處


元組(Tuple)是陣列的一種,在處理特定數量和型別的資料時很好用,它可以明確點出哪個位置要對應哪種型別,是有序性

以下利用幾個情境範例來說明:

情境一:單純賦值

語法:中括號裡包住 type,每個 type 用逗號隔開,例如 [string, string, number]

person 是一個元組,預設只接受三個值,型別分別是 string, string, number
如果在特定位置給予不符合型別的值會報錯
多寫了沒定義的值也會報錯

let person: [string, string, number];
person = ['Hannah', 'beautiful', 18]; // ✅ pass
person = ['Hannah', '18', 'beautiful']; // ❌ Type 'string' is not assignable to type 'number'
person = ['Hannah', 18, 'beautiful', '123']; // ❌ Source has 4 element(s) but target allows only 3

情境二:越界的元素

我們可以在 Tuple 上使用陣列處理方法,但需要注意某些操作可能會破壞 Tuple 的型別結構,例如:map(), push()/pop(), shift()/unshift(), splice() 等, 導致 TypeScript 編譯器報錯

let person: [string, string, number];

person.push('super beautiful');  // ❓ 猜猜看 push 後的結果
person.push(true);  // ❓ 猜猜看 push 後的結果

答案是...

// 前面略
person.push('super beautiful'); // ✅ Pass
person.push(true);  // ❌ Argument of type 'boolean' is not assignable to parameter of type 'string | number'.

person.push('super beautiful'); 居然沒報錯,不是只有定義三個值的型別嗎?
原因是索引 3+ 之後的值,就算沒別定義,型別一樣會被限制為元組中每個型別的聯合型別,以此範例來說就是 string | number
這也是為什麼塞入 string 時不會報錯,而下一行 person.push(true); 會報錯的原因


可選屬性讓你的 Tuple 在嚴格中帶點彈性

元組可以透過 來表示可選屬性,這是它的專屬福利,相反的陣列就無法直接這樣做
但要注意的是可選屬性只能出現在「末尾」,且會影響元組長度

  • 末尾的限制:這是因為在元組中,每個值的位置都對應到特定的型別,如果中間有可選元素,將無法確定後面元素的確切位置和型別
  • 長度的影響:當元組包含一個或多個可選元素時,長度就變成了一個範圍,而不是一個固定數值。例如,元組 [string, number, boolean?] 的長度可能為 2 或 3
let tuple: [string, number, boolean?];
tuple = ["hello", 42];  // ✅ Pass
tuple = ["hello", 42, true];  // ✅ Pass

// ❌ 下面會報錯
// tuple = ["hello"];  // 第二個元素(number)是必須的
// tuple = ["hello", true];  // 第二個元素的型別不匹配

利用 Readonly 來保護你的元組

如果你想要保護你的元組不被修改,可以在最前面加上 readonly 關鍵字

// Readonly tuple
const readonlyTuple: readonly [number, boolean, string] = [5, true, 'test'];
readonlyTuple.push('hello'); // ❌ Property 'push' does not exist on type 'readonly [number, boolean, string]'. 
readonlyTuple[0] *= 5; // ❌ Cannot assign to '0' because it is a read-only property.

元組可以使用剩餘運算符和解構

JavaScript 對陣列的解構方法也可以拿來用在元組上

  • 基本解構
let tuple1: [number, string, boolean] = [1, "hello", true];
let [a, b, c] = tuple1;

console.log(a); // 1
console.log(b); // "hello"
console.log(c); // true
  • 一般是不會這樣寫!這段只是想表達可以忽略元素,只解構特定元素
let tuple2: [number, string, boolean] = [1, "hello", true];
let [x, , z] = tuple2;

console.log(x); // 1
console.log(z); // true
  • 想抓取部分元素,並將剩餘的元素作為陣列保留時,可使用剩餘運算符 ...
let tuple3: [number, string, boolean, number] = [1, "hello", true, 2024];
let [first, second, ...rest] = tuple3; // 解構前兩個元素,將剩餘元素放入另一個陣列

console.log(first);  // 1
console.log(second); // "hello"
console.log(rest);   // [true, 2024]
  • 解構可選元素,要注意這些元素可能是 undefined給予初始值條件檢查是一種解法
let tuple4: [string, number?, boolean?] = ["hello"];
let [p1, p2 = 0, p3 = false] = tuple4; // 解構可選元素,並給予初始值

console.log(a);  // "hello"
console.log(b);  // 0
console.log(c); // false
  • 沒給初始值,不會報錯,只是 p2p3 會得到 undefined
let tuple4: [string, number?, boolean?] = ["hello"];
let [p1, p2, p3] = tuple4;

每天講的內容有推到 github 上喔

結論

在實踐中,既然都選擇使用 tuple 了,那最好是保持 Tuple 的不變性,以避免型別相關的錯誤

屬性 陣列 元組
特性 通常包含單一類型的多個元素 可以包含不同類型的多個元素
範例 string[], Array<string> [string, number]
使用時機 同質性高的資料集合 具有固定數量和多種型別的資料結構
靈活性 低(結構固定)
記憶體使用和存取速度 受動態操作影響 元組數量固定的特性,有助於 JavaScript V8 引擎進行一些優化,但是十分微小

References


上一篇
TypeScript 初學者也能看的學習指南 06 - Array 陣列
下一篇
TypeScript 初學者也能看的學習指南 08 - Function 函式
系列文
TypeScript 初學者也能看的學習指南16
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言